import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import pandas as pd
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt

class ReactionApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Reaction Dynamics GUI')
        self.geometry('800x600')

        # Widgets
        self.load_button = tk.Button(self, text="Load Excel", command=self.load_excel)
        self.load_button.pack(pady=10)

        self.run_button = tk.Button(self, text="Run Simulation", command=self.run_simulation, state=tk.DISABLED)
        self.run_button.pack(pady=10)

        # Treeview Widget for displaying DataFrame
        self.tree = ttk.Treeview(self)
        self.tree.pack(pady=20, expand=True, fill='both')
        self.tree.bind('<Double-1>', self.on_double_click)  # Bind double-click event

    def load_excel(self):
        filepath = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx *.xls")])
        if filepath:
            self.df = pd.read_excel(filepath, sheet_name='Sheet1', engine='openpyxl')
            self.display_dataframe(self.df.iloc[3:23, 3:25])
            self.run_button.config(state=tk.NORMAL)
            messagebox.showinfo("Info", "Excel loaded successfully!")

    def display_dataframe(self, df):
        self.tree.delete(*self.tree.get_children())  # Clear existing data
        self.tree["column"] = list(df.columns)
        self.tree["show"] = "headings"
        for column in self.tree["column"]:
            self.tree.heading(column, text=column, command=lambda _col=column: \
                self.treeview_sort_column(self.tree, _col, False))
            self.tree.column(column, width=100)

        for index, row in df.iterrows():
            self.tree.insert("", "end", iid=index, values=list(row))

    def treeview_sort_column(self, tv, col, reverse):
        l = [(tv.set(k, col), k) for k in tv.get_children('')]
        l.sort(reverse=reverse)

        for index, (val, k) in enumerate(l):
            tv.move(k, '', index)

        tv.heading(col, command=lambda: self.treeview_sort_column(tv, col, not reverse))

    def on_double_click(self, event):
        region = self.tree.identify("region", event.x, event.y)
        if region == "cell":
            column = self.tree.identify_column(event.x)
            row = self.tree.identify_row(event.y)
            current_value = self.tree.item(row, 'values')[int(column[1:]) - 1]
            new_value = simpledialog.askstring("Edit", "Edit value:", initialvalue=current_value)
            if new_value is not None:
                self.df.loc[int(row), self.tree.column(column, "id")] = new_value
                self.tree.set(row, column, new_value)

    def run_simulation(self):
        # 强制转换为数值类型，并处理无法转换的数据
        try:
            # 因为加载的数据是iloc[3:23, 3:25]，这意味着你的参数a, A, E应该相应调整
            # 假设最后两列是A和E
            opt_a = self.df.iloc[:, :-2].apply(pd.to_numeric, errors='coerce').values
            opt_A = pd.to_numeric(self.df.iloc[:, -2], errors='coerce').values
            opt_E = pd.to_numeric(self.df.iloc[:, -1], errors='coerce').values * 4185.8518208

            # 检查是否有NaN值
            if np.isnan(opt_A).any() or np.isnan(opt_E).any():
                messagebox.showerror("Error", "Data contains non-numeric values that cannot be converted.")
                return
        except Exception as e:
            messagebox.showerror("Error", f"Error converting data: {e}")
            return

        opt = {'a': opt_a, 'A': opt_A, 'E': opt_E}

        X0 = np.zeros(20)  # 根据opt_a的列数调整
        X0[0] = 1.0
        t_span = [0, 100]

        sol = solve_ivp(lambda t, X: self.react_ode(t, X, self.example_temperature, opt), t_span, X0, method='BDF')

        if not sol.success:
            messagebox.showerror("Error", "ODE solver did not converge")
            return

        plt.figure()
        plt.plot(sol.t, sol.y.T)
        plt.xlabel('Time (s)')
        plt.ylabel('Concentration')
        plt.title('Reaction Dynamics')
        plt.show()

    @staticmethod
    def react_ode(t, X, T_func, opt):
        R = 8.314
        dXdt = np.zeros(20)
        T = T_func(t)
        for j in range(20):
            s = 0
            for i in range(20):
                s += X[i] * opt['a'][i, j] * opt['A'][i] * np.exp(-opt['E'][i] / (R * T))
            dXdt[j] = s - X[j] * opt['A'][j] * np.exp(-opt['E'][j] / (R * T))
        return dXdt

    @staticmethod
    def example_temperature(t):
        return 298 + 10 * np.sin(t)

if __name__ == "__main__":
    app = ReactionApp()
    app.mainloop()
